﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lilium.LParse;
using Lilium.LParse.TextExtensions;
using System.Diagnostics;
using Lilium;
using System.IO;

namespace PerformanceTests
{
    class Program
    {
        static void Main(string[] args)
        {
            Skip();
            HexNumbers();
            FloatNumbers();
            //  FloatNumbersFromFile();
        }

        static void Skip()
        {
            var sb = new StringBuilder();
            for (var i = 0; i < 1000000; i++)
                sb.Append(i.ToString());
            var text = sb.ToString();

            var skip = TextParsers.SkipWhile(_ => true);

            GC.Collect(2);
            GC.WaitForFullGCComplete();

            var sw = new Stopwatch();
            sw.Start();
            skip.Parse(text); // using try combinator to suppess excepted values collection. This yelds ~20% speed up.
            sw.Stop();

            Console.WriteLine("--- Skip parser ---");
            PrintStatistics(text.Length, sw.Elapsed);
        }

        static void FloatNumbers()
        {
            var sb = new StringBuilder();
            var rand = new System.Random();
            double control = 0;
            for (int i = 0; i < 1000000; i++)
            {
                double d = 10000 * rand.NextDouble() * (rand.NextDouble() > 0.7 ? -1.0 : 1.0);
                sb.Append(d).Append(' ');
                control += d;
            }
            var text = sb.ToString();

            var digit = from ch in TextParsers.Range('0', '9') select (long)ch - '0';
            var decimalSeparator = TextParsers.OneOf(".,");
            var sign = from ch in TextParsers.Char('-') select -1;
            var whitesapce = TextParsers.OneOf(" \n");

            var integral = digit.Fold1Many1((a, d) => a * 10 + d);

            var fraction = from _ in decimalSeparator
                           from collected in digit.FoldMany1((a, d) => Tuple.Create(a.Item1 * 10 + d, a.Item2 + 1), Tuple.Create(0L, 0))
                           select collected.Item1 / Math.Pow(10, collected.Item2);

            var exponent = from _ in TextParsers.OneOf("eE")
                           from s in sign
                           from i in integral
                           select Math.Pow(10, i * s);

            var real = from s in sign.Optional(1)
                       from i in integral
                       from f in fraction
                       from e in exponent.Optional(1)
                       select (i + f) * s * e;

            var sum = ParserCombinators.Fold1Many1(
                from r in real
                from _ in whitesapce
                select r,
                (a, r) => a + r);

            var sw = new Stopwatch();

            GC.Collect(2);
            GC.WaitForFullGCComplete();

            sw.Start();
            var result = sum.Try().Parse(text); // using try combinator to suppess excepted values collection. This yelds ~20% speed up.
            sw.Stop();

            Console.WriteLine("--- Real numbers parser ---");
            Console.WriteLine("Parsed value: {0}", result);
            Console.WriteLine("Control value: {0}", control);
            PrintStatistics(text.Length, sw.Elapsed);
        }

        static void FloatNumbersFromFile()
        {
            var rand = new System.Random();
            double control = 0;
            using (var writer = new StreamWriter("numbers_large.txt"))
                for (int i = 0; i < 5000000; i++)
                {
                    double d = 10000 * rand.NextDouble() * (rand.NextDouble() > 0.7 ? -1.0 : 1.0);
                    writer.Write(d);
                    writer.Write(' ');
                    control += d;
                }

            var digit = from ch in TextParsers.Range('0', '9') select (long)ch - '0';
            var decimalSeparator = TextParsers.OneOf(".,");
            var sign = from ch in TextParsers.Char('-') select -1;
            var whitesapce = TextParsers.OneOf(" \n");

            var integral = digit.Fold1Many1((a, d) => a * 10 + d);

            var fraction = from _ in decimalSeparator
                           from collected in digit.FoldMany1((a, d) => Tuple.Create(a.Item1 * 10 + d, a.Item2 + 1), Tuple.Create(0L, 0))
                           select collected.Item1 / Math.Pow(10, collected.Item2);

            var exponent = from _ in TextParsers.OneOf("eE")
                           from s in sign
                           from i in integral
                           select Math.Pow(10, i * s);

            var real = from s in sign.Optional(1)
                       from i in integral
                       from f in fraction
                       from e in exponent.Optional(1)
                       select (i + f) * s * e;

            var sum = ParserCombinators.Fold1Many1(
                from r in real
                from _ in whitesapce
                select r,
                (a, r) => a + r);

            var sw = new Stopwatch();

            using (var reader = new StreamReader("numbers_large.txt"))
            {
                sw.Start();
                var state = new TextReaderParserState(reader);
                var result = sum(state);
                sw.Stop();

                Console.WriteLine("--- Real numbers parser (from file) ---");
                Console.WriteLine("Parsed value: {0}", result);
                Console.WriteLine("Control value: {0}", control);
                PrintStatistics(state.Position, sw.Elapsed);
            }

            File.Delete("numbers_large.txt");
        }

        static void HexNumbers()
        {
            var sw = new Stopwatch();

            var values = "1234567890abcdef";
            var random = new Random();
            var sb = new StringBuilder();
            sw.Start();
            for (int i = 0; i < 10000000; i++)
            {
                var rnd = random.Next(20);
                if (rnd <= 15)
                    sb.Append(values[rnd]);
                else
                    sb.Append(' ');
            }
            var text = sb.ToString();

            var hexDigit = ParserCombinators.Choise(new[] {
                (from ch in TextParsers.Range('0', '9') select ch - '0'),
                (from ch in TextParsers.Range('a', 'f') select ch - 'a' + 10),
                (from ch in TextParsers.Range('A', 'F') select ch - 'A' + 10) });

            var whitespace = TextParsers.Char(' ').SkipMany();

            var hexNumber = from value in hexDigit.Fold1Many1((accum, value) => accum * 16 + value)
                            from _ in whitespace
                            select value;

            var numParsers = from _ in whitespace
                             from value in hexNumber.Many()
                             select value;

            GC.Collect(2);
            GC.WaitForFullGCComplete();

            sw.Start();
            var res = numParsers.Try().Parse(text);
            sw.Stop();

            Console.WriteLine("--- Hex numbers parser ---");
            Console.WriteLine("Numbers count: {0}", res.Count);
            PrintStatistics(text.Length, sw.Elapsed);
        }

        static void PrintStatistics(long symbolsCount, TimeSpan parseTime)
        {
            Console.WriteLine("Parse time: {0}", parseTime);
            Console.WriteLine("Symbols total: {0}", symbolsCount);
            Console.WriteLine("Symbols per second: {0}", parseTime != TimeSpan.Zero ? Math.Truncate(symbolsCount / parseTime.TotalMilliseconds * 1000) : double.PositiveInfinity);
        }
    }
}
